#pragma once

#include <blu\buffer.h>
#include <list>
#include <vector>
#include <memory>
#include <optional>
#include "presenter.h"
#include "vertex_semantic.h"
#include <algorithm>
#include <numeric>

namespace teresa
{
	template <typename Cont>
	class inherit_container_concept
		:protected Cont
	{
	protected:
		typedef Cont base_container;
		using Cont::Cont;
	public:
		using Cont::value_type;
		using Cont::reference;
		using Cont::const_reference;
		using Cont::iterator;
		using Cont::const_iterator;
		using Cont::difference_type;
		using Cont::size_type;

		using Cont::begin;
		using Cont::end;
		using Cont::cbegin;
		using Cont::cend;
		using Cont::swap;
		using Cont::size;
		using Cont::max_size;
		using Cont::empty;
	};

	template <typename Cont>
	class inherit_sequence_container_concept
		:protected inherit_container_concept<Cont>
	{
	public:
		using inherit_container_concept<Cont>::inherit_container_concept;
		using base_container::insert;
		using base_container::erase;
		using base_container::clear;
		using base_container::assign;
	};

	class primitive_semantic_group
		:public inherit_sequence_container_concept<std::list<std::reference_wrapper<vertex_semantic>>>
	{
	public:
		using base_container::push_back;
		using base_container::push_front;
		std::uint32_t stride() const
		{
			auto rend(end());
			auto result = std::accumulate(begin(), end(), static_cast<std::uint32_t>(0),
				[](std::uint32_t n, const vertex_semantic &semantic_) { return blu::get_size(semantic_.type()) + n; });
			return result;
		}
	};

	class primitive_semantic
		:public inherit_container_concept<std::list<primitive_semantic_group>>
	{
	public:
		using base_container::erase;
		void insert(value_type &&val_)
		{
			push_back(std::move(val_));
		}
	};

	class primitive
	{
	private:
		presenter &_presenter;
		std::list<blu::generic_buffer> _vbufs;
		std::optional<blu::index_buffer> _ibuf;
		std::uint_fast32_t _szVertices, _szIndices;

		void _allocVBufs(const primitive_semantic &primsemantic, std::uint32_t szvertices_)
		{
			for (auto &group : primsemantic)
			{
				blu::generic_buffer vbuf(_presenter.default_device(), group.stride() * szvertices_);
				_vbufs.push_back(std::move(vbuf));
			}
		}
	public:
		typedef blu::generic_buffer::size_type vertices_size_type;

		typedef blu::index_buffer::size_type indices_size_type;

		primitive(presenter &presenter_, const primitive_semantic &primsemantic, vertices_size_type szvertices_)
			:_presenter(presenter_),
			_szVertices(szvertices_),
			_szIndices(szvertices_)
		{
			_allocVBufs(primsemantic, szvertices_);
		}

		template <typename ContiguousContainer>
		primitive(presenter &presenter_, const primitive_semantic &primsemantic, vertices_size_type szvertices_, const ContiguousContainer &indices_)
			:_presenter(presenter_),
			_szVertices(szvertices_),
			_szIndices(indices_.size()),
			_ibuf(presenter_.default_device(), indices_)
		{
			_allocVBufs(primsemantic, szvertices_);
		}

		vertices_size_type vertices_size() const
		{
			return _szVertices;
		}

		indices_size_type indices_size() const
		{
			return _szIndices;
		}

		template <typename ElemTy>
		void write(const primitive_semantic_group &group, const ElemTy *ptr, vertices_size_type szvertices)
		{
			auto groupNum = group.number();
			auto iBuf = std::next(_vbufs.begin(), groupNum);
			iBuf->write(ptr, szvertices);
		}

		template <typename ContiguousContainer>
		void write(const primitive_semantic_group &group, const ContiguousContainer &arr)
		{
			auto groupNum = group.number();
			auto iBuf = std::next(_vbufs.begin(), groupNum);
			iBuf->write(arr);
		}

		void mirror(const primitive_semantic_group &group)
		{

		}
	};
}